package videosearch;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.DataLine.Info;

/**
 * @author Giulio & John & Nate This class plays sounds - written by the TA,
 * extended by John and Nate
 */
public class PlaySound {

    private InputStream waveStream;
    private String path;
    private final int EXTERNAL_BUFFER_SIZE = 3550; // 1/25th of a second of audio
    AudioInputStream audioInputStream = null;
    SourceDataLine dataLine = null;
    byte[] audioStream;
    AudioFormat audioFormat = null;
    int index = 0;

    /**
     * CONSTRUCTOR Takes the path to grab the sound file from
     */
    public PlaySound(String path) {
        this.path = path;
    }

    // Sets up the stream to play audio files
    public void setupStream() throws PlayWaveException {
        ArrayList<Byte> audioByteArrayList = new ArrayList<Byte>();
        // opens the inputStream    	    	
        try {
            waveStream = new BufferedInputStream(new FileInputStream(path));
            audioInputStream = AudioSystem.getAudioInputStream(this.waveStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return;
        } catch (UnsupportedAudioFileException e1) {
            throw new PlayWaveException(e1);
        } catch (IOException e1) {
            throw new PlayWaveException(e1);
        }


        // Obtain the information about the AudioInputStream
        audioFormat = audioInputStream.getFormat();
        Info info = new Info(SourceDataLine.class, audioFormat);

        // create a new dataline
        try {
            dataLine = (SourceDataLine) AudioSystem.getLine(info);
        } catch (LineUnavailableException e1) {
            throw new PlayWaveException(e1);
        }

        // read into the audio buffer and add them to the player data
        int readBytes = 0;
        byte[] audioBuffer;
        audioBuffer = new byte[this.EXTERNAL_BUFFER_SIZE];
        try {
            while (readBytes != -1) {
                readBytes = audioInputStream.read(audioBuffer, 0,
                        audioBuffer.length);
                if (readBytes >= 0) {
                    for (byte audioByte : audioBuffer) {
                        audioByteArrayList.add(audioByte);
                    }
                }
            }
        } catch (IOException e1) {
            throw new PlayWaveException(e1);
        }
        audioStream = new byte[audioByteArrayList.size()];
        for (int i = 0; i < audioByteArrayList.size(); i++) {
            audioStream[i] = audioByteArrayList.get(i);
        }
    }

    // open the data line from the file, with a big buffer, and start playing
    public void openDataLine() {
        try {
            dataLine.open(audioFormat, this.EXTERNAL_BUFFER_SIZE * 10);
        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // Starts the music :P
        dataLine.start();
    }

    // Close the input data line
    public void closeDataLine() {
        dataLine.drain();
        dataLine.close();
    }

    // close out the audio stream
    public void closeAudioStreams() {
        try {
            audioInputStream.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        closeDataLine();
    }

    // Plays the next frame of audio
    public void playNextFrame() throws PlayWaveException {
        int length;

        if (audioStream.length < index * EXTERNAL_BUFFER_SIZE) {
            return;
        }

        if (audioStream.length < (index + 1) * EXTERNAL_BUFFER_SIZE) {
            length = audioStream.length - index * EXTERNAL_BUFFER_SIZE - 1;
        } else {
            length = EXTERNAL_BUFFER_SIZE;
        }

        //System.out.println(dataLine.available());
        try {
            dataLine.write(audioStream, index * EXTERNAL_BUFFER_SIZE, length);
        } catch (Exception e) {
            System.out.println();
        }
        index++;
    }

    // Plays the audio from a specific frame
    public void playSpecifiedFrame(int frame) throws PlayWaveException {
        goToSpecifiedFrame(frame);
        playNextFrame();
    }

    // moves the counter to a specified frame
    public void goToSpecifiedFrame(int frame) throws PlayWaveException {
        index = frame;
    }

    // Resets the audio stream to the beginning of the stream
    public void resetStreamToBeginning() throws PlayWaveException {
        try {
            audioInputStream.close();
            closeDataLine();
            setupStream();
            openDataLine();
        } catch (IOException e1) {
            throw new PlayWaveException(e1);
        }
    }
}


// Class for exception handling
class PlayWaveException extends Exception {

    private static final long serialVersionUID = -6679025102624288219L;

    public PlayWaveException(String message) {
        super(message);
    }

    public PlayWaveException(Throwable cause) {
        super(cause);
    }

    public PlayWaveException(String message, Throwable cause) {
        super(message, cause);
    }
}
